[HVM][VMX] Use CPUID instruction virtualization to workaround VMXAssist 4G limit.
authorkfraser@localhost.localdomain <kfraser@localhost.localdomain>
Thu, 19 Oct 2006 14:49:16 +0000 (15:49 +0100)
committerkfraser@localhost.localdomain <kfraser@localhost.localdomain>
Thu, 19 Oct 2006 14:49:16 +0000 (15:49 +0100)
Address space access limit in VMXAssist is 4G, because IA-32 only has
4GB virtual address space which VMXassist can use to map physical
memory. The issue is, win2k3 server with more than 4G memory will put
AP GDT above 4G, so when AP changes its mode from real mode to PAE
paging mode, the long jump instrction it uses need access AP GDT
entries which resides above 4G, but because of this constraint, it can
not access GDT and so fails boot.

Signed-off-by: Xin Li <xin.b.li@intel.com>
tools/firmware/vmxassist/util.c
tools/firmware/vmxassist/util.h
tools/firmware/vmxassist/vm86.c
xen/arch/x86/hvm/vmx/vmx.c

index 0181fe702c1399afe07bd9808954c5dd6777111c..fbfc79a1a38a0cbe67084eee91394d060cbc6eff 100644 (file)
@@ -29,6 +29,31 @@ static void putchar(int);
 static char *printnum(char *, unsigned long, int);
 static void _doprint(void (*)(int), char const *, va_list);
 
+void
+cpuid_addr_value(uint64_t addr, uint64_t *value)
+{
+       uint32_t addr_low   = (uint32_t)addr;
+       uint32_t addr_high  = (uint32_t)(addr >> 32);
+       uint32_t value_low, value_high;
+       static unsigned int addr_leaf;
+
+       if (!addr_leaf) {
+               unsigned int eax, ebx, ecx, edx;
+               __asm__ __volatile__(
+                       "cpuid"
+                       : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
+                       : "0" (0x40000000));
+               addr_leaf = eax + 1;
+       }
+
+       __asm__ __volatile__(
+               "cpuid"
+               : "=c" (value_low), "=d" (value_high)
+               : "a" (addr_leaf), "0" (addr_low), "1" (addr_high)
+               : "ebx");
+
+       *value = (uint64_t)value_high << 32 | value_low;
+}
 
 void
 dump_regs(struct regs *regs)
index b2ace92b8f9973d9f49556943e396cd7da301b39..c426f4e846c9a974604d8fd1b249002fccb318fd 100644 (file)
@@ -31,6 +31,7 @@
 
 struct vmx_assist_context;
 
+extern void cpuid_addr_value(uint64_t addr, uint64_t *value);
 extern void hexdump(unsigned char *, int);
 extern void dump_regs(struct regs *);
 extern void dump_vmx_context(struct vmx_assist_context *);
index 8c620a4d5ce88b4afb2b6c46235296ff38c8bf52..f997fc22ab3e0483dddd1a73bd1465720f33648f 100644 (file)
@@ -56,8 +56,8 @@ static char *rnames[] = { "ax", "cx", "dx", "bx", "sp", "bp", "si", "di" };
 #define PT_ENTRY_PRESENT 0x1
 
 /* We only support access to <=4G physical memory due to 1:1 mapping */
-static unsigned
-guest_linear_to_real(uint32_t base)
+static uint64_t
+guest_linear_to_phys(uint32_t base)
 {
        uint32_t gcr3 = oldctx.cr3;
        uint64_t l2_mfn;
@@ -89,23 +89,32 @@ guest_linear_to_real(uint32_t base)
                l2_mfn = ((uint64_t *)(long)gcr3)[(base >> 30) & 0x3];
                if (!(l2_mfn & PT_ENTRY_PRESENT))
                        panic("l3 entry not present\n");
-               l2_mfn &= 0x3fffff000ULL;
+               l2_mfn &= 0xffffff000ULL;
 
-               l1_mfn = ((uint64_t *)(long)l2_mfn)[(base >> 21) & 0x1ff];
+               if (l2_mfn & 0xf00000000ULL) {
+                       printf("l2 page above 4G\n");
+                       cpuid_addr_value(l2_mfn + 8 * ((base >> 21) & 0x1ff), &l1_mfn);
+               } else
+                       l1_mfn = ((uint64_t *)(long)l2_mfn)[(base >> 21) & 0x1ff];
                if (!(l1_mfn & PT_ENTRY_PRESENT))
                        panic("l2 entry not present\n");
 
                if (l1_mfn & PDE_PS) { /* CR4.PSE is ignored in PAE mode */
-                       l0_mfn = l1_mfn & 0x3ffe00000ULL;
+                       l0_mfn = l1_mfn & 0xfffe00000ULL;
                        return l0_mfn + (base & 0x1fffff);
                }
 
-               l1_mfn &= 0x3fffff000ULL;
+               l1_mfn &= 0xffffff000ULL;
 
-               l0_mfn = ((uint64_t *)(long)l1_mfn)[(base >> 12) & 0x1ff];
+               if (l1_mfn & 0xf00000000ULL) {
+                       printf("l1 page above 4G\n");
+                       cpuid_addr_value(l1_mfn + 8 * ((base >> 12) & 0x1ff), &l0_mfn);
+               } else
+                       l0_mfn = ((uint64_t *)(long)l1_mfn)[(base >> 12) & 0x1ff];
                if (!(l0_mfn & PT_ENTRY_PRESENT))
                        panic("l1 entry not present\n");
-               l0_mfn &= 0x3fffff000ULL;
+
+               l0_mfn &= 0xffffff000ULL;
 
                return l0_mfn + (base & 0xfff);
        }
@@ -114,6 +123,7 @@ guest_linear_to_real(uint32_t base)
 static unsigned
 address(struct regs *regs, unsigned seg, unsigned off)
 {
+       uint64_t gdt_phys_base;
        unsigned long long entry;
        unsigned seg_base, seg_limit;
        unsigned entry_low, entry_high;
@@ -129,8 +139,13 @@ address(struct regs *regs, unsigned seg, unsigned off)
            (mode == VM86_REAL_TO_PROTECTED && regs->cs == seg))
                return ((seg & 0xFFFF) << 4) + off;
 
-       entry = ((unsigned long long *)
-                 guest_linear_to_real(oldctx.gdtr_base))[seg >> 3];
+       gdt_phys_base = guest_linear_to_phys(oldctx.gdtr_base);
+       if (gdt_phys_base != (uint32_t)gdt_phys_base) {
+               printf("gdt base address above 4G\n");
+               cpuid_addr_value(gdt_phys_base + 8 * (seg >> 3), &entry);
+       } else
+               entry = ((unsigned long long *)(long)gdt_phys_base)[seg >> 3];
+
        entry_high = entry >> 32;
        entry_low = entry & 0xFFFFFFFF;
 
@@ -804,6 +819,7 @@ pop(struct regs *regs, unsigned prefix, unsigned opc)
 static int
 load_seg(unsigned long sel, uint32_t *base, uint32_t *limit, union vmcs_arbytes *arbytes)
 {
+       uint64_t gdt_phys_base;
        unsigned long long entry;
 
        /* protected mode: use seg as index into gdt */
@@ -815,8 +831,12 @@ load_seg(unsigned long sel, uint32_t *base, uint32_t *limit, union vmcs_arbytes
                return 1;
        }
 
-       entry = ((unsigned long long *)
-                 guest_linear_to_real(oldctx.gdtr_base))[sel >> 3];
+       gdt_phys_base = guest_linear_to_phys(oldctx.gdtr_base);
+       if (gdt_phys_base != (uint32_t)gdt_phys_base) {
+               printf("gdt base address above 4G\n");
+               cpuid_addr_value(gdt_phys_base + 8 * (sel >> 3), &entry);
+       } else
+               entry = ((unsigned long long *)(long)gdt_phys_base)[sel >> 3];
 
        /* Check the P bit first */
        if (!((entry >> (15+32)) & 0x1) && sel != 0)
index cfd1b2d40005e4cc85aebf342fdb8a93725301be..af30180e5bd5e9d6e883ecff622a22026546492b 100644 (file)
@@ -921,7 +921,32 @@ static void vmx_do_cpuid(struct cpu_user_regs *regs)
     if ( input == CPUID_LEAF_0x4 )
     {
         cpuid_count(input, count, &eax, &ebx, &ecx, &edx);
-        eax &= NUM_CORES_RESET_MASK;  
+        eax &= NUM_CORES_RESET_MASK;
+    }
+    else if ( input == 0x40000003 )
+    {
+        /*
+         * NB. Unsupported interface for private use of VMXASSIST only.
+         * Note that this leaf lives at <max-hypervisor-leaf> + 1.
+         */
+        u64 value = ((u64)regs->edx << 32) | (u32)regs->ecx;
+        unsigned long mfn = get_mfn_from_gpfn(value >> PAGE_SHIFT);
+        char *p;
+
+        DPRINTK("Input address is 0x%"PRIx64".\n", value);
+
+        /* 8-byte aligned valid pseudophys address from vmxassist, please. */
+        if ( (value & 7) || (mfn == INVALID_MFN) ||
+             !v->arch.hvm_vmx.vmxassist_enabled )
+            domain_crash_synchronous();
+
+        p = map_domain_page(mfn);
+        value = *((uint64_t *)(p + (value & (PAGE_SIZE - 1))));
+        unmap_domain_page(p);
+
+        DPRINTK("Output value is 0x%"PRIx64".\n", value);
+        ecx = (u32)(value >>  0);
+        edx = (u32)(value >> 32);
     }
     else if ( !cpuid_hypervisor_leaves(input, &eax, &ebx, &ecx, &edx) )
     {